'use strict';

const Utility = require('./utility');



class Observer{
    constructor() {

        this.setPrivate('handle',  { });
    }

    static getEvent(iEvent) {

        return Object.assign(
            { },
            (typeof iEvent == 'string')  ?  {type: iEvent}  :  iEvent,
            arguments[1]
        );
    }

    static match(iEvent, iHandle) {

        for (var iKey in iHandle) {

            let iRegExp = iEvent[iKey] instanceof RegExp;

            switch ( iHandle[iKey].constructor.name ) {
                case 'RegExp':
                    if ( iRegExp ) {
                        if (iEvent[iKey].toString() != iHandle[iKey].toString())
                            return;
                        break;
                    }
                case 'String':    {
                    if (! (iEvent[iKey] || '')[iRegExp ? 'test' : 'match'](
                        iHandle[iKey]
                    ))
                        return;
                    break;
                }
                case 'Function':
                    if (typeof iEvent[iKey] != 'function')  break;
                default:
                    if (iEvent[iKey] !== iHandle[iKey])  return;
            }
        }

        return iHandle;
    }

    toString() {

        return  '[object ' + this.constructor.name + ']';
    }

    valueOf(iEvent, iKey) {

        if (! iEvent)  return  this.__handle__;

        return  (! iKey)  ?  this.__handle__[iEvent]  :
            this.__handle__[iEvent].map(function () {

                return  arguments[0][ iKey ];
            });
    }

    _register(method, iEvent, iCallback) {

        iEvent = Observer.getEvent(iEvent,  {handler: iCallback});

        var iHandle = this.__handle__[iEvent.type] =
                this.__handle__[iEvent.type]  ||  [ ];

        for (var i = 0;  iHandle[i];  i++)
            if (Utility.isEqual(iHandle[i], iEvent))  return this;

        iHandle[ method ]( iEvent );

        return this;
    }

    on(event, callback) {

        return  this._register('push', event, callback);
    }

    before(event, callback) {

        return  this._register('unshift', event, callback);
    }

    emit(iEvent, iData) {

        iEvent = Observer.getEvent( iEvent );

        return  (this.__handle__[iEvent.type] || [ ]).reduce(
            (function (_Data_, iHandle) {

                if (! Observer.match(iEvent, iHandle))  return _Data_;

                var iResult = iHandle.handler.call(this, iEvent, _Data_);

                return  (iResult != null)  ?  iResult  :  _Data_;

            }).bind( this ),
            iData
        );
    }

    off(iEvent, iCallback) {

        iEvent = Observer.getEvent(iEvent,  {handler: iCallback});

        this.__handle__[iEvent.type] = this.__handle__[iEvent.type].map(
            function (iHandle) {

                return  Observer.match(iEvent, iHandle)  ?  null  :  iHandle;
            }
        );

        return this;
    }

    one() {
        var _This_ = this,  iArgs = Array.apply(null, arguments);

        var iCallback = iArgs.slice(-1)[0];

        iCallback = (typeof iCallback == 'function')  &&  iArgs.pop();

        var iPromise = new Promise(function (iResolve) {

                _This_.on.apply(_This_,  iArgs.concat(function () {

                    _This_.off.apply(_This_,  iArgs.concat( arguments.callee ));

                    if ( iCallback )  return  iCallback.apply(this, arguments);

                    iResolve( arguments[1] );
                }));
            });

        return  iCallback ? this : iPromise;
    }
}

module.exports = Observer;